package org.lisy.java.encrypt;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.SignatureException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

/**
 * 加密算法
 * 
 * @author lisy
 */
public class EncryptOperate {

	public static void main(String[] args) {
		
		String data = "Hello World!";

		/**
		 * Base64: 把不可能打印的字符用可打印的字符标识，便于网络传输
		 *  - base64编码：把二进制数据转为字符
		 *  - base64解码：把字符转为二进制数据
		 * 摘要算法: 不可逆的加密，用于效验数据完整性的
		 *  - MD5: 将明文输出为128bits的字符串，速度快
		 *  - SHA1: 是 MD5 类似的信息摘要算法，比MD5更加安全
		 * 对称加密: 发送方和接收方的密钥是一样的
		 *  - DES: 在DES中，将明文分为两半，然后再进行进一步处理，DES中使用的共享密钥的比特大小较小，因此它被认为在安全性上比 AES 略差
		 *  - AES: 在AES中，整个块不进行除法，整个块一起处理以生成密文，速度快。AES比DES更具灵活性，允许包括128、192、256位在内的各种长度的文本，DES允许对64位固定文本进行加密
		 * 非对称加密: 发送方和接收方的密钥不一样，有公钥和私钥的概念，基本上公钥是公开的
		 *  - RSA: 支持公钥加密，私钥解密或者是私钥加密，公钥解密两种方式，可以用于加密和验签，支付宝支付对接时用的加密方式就是 RSA
		 *  加密方式：B 传加密数据给 A
		 *   1、A 生成公钥和私钥，私钥自己保留，公钥任何人可以获取
		 *   2、B 拿到公钥，将数据通过公钥加密
		 *   3、A 收到密文，通过私钥解密
		 *  验签方式：A 传消息给 B (验签是指通过私钥对消息进行签名，使得消息无法篡改和伪造)
		 *   1、A 生成公钥和私钥，私钥自己保留，公钥任何人可以获取
		 *   2、A 使用私钥对消息加签，并将加签后的消息传给 B
		 *   3、B 通过公钥验签，如果返回是true则说明消息是 A 发过来的且未被篡改
		 */
		Base64Operate(data);
		
		MD5Encode(data);
		
		SHA1Encode(data);
		
		DESOperate(data);
		
		AESOperate(data);
		
		RSAOperate(data);
	}

	private static void RSAOperate(String data) {
		try {
			KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
			keyPairGenerator.initialize(512);
			KeyPair keyPair = keyPairGenerator.generateKeyPair();
			// 公钥
			RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
			// 私钥
			RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
			System.out.println("RSA public key: " + java.util.Base64.getEncoder().encodeToString(rsaPublicKey.getEncoded()));
			System.out.println("RSA private key: " + java.util.Base64.getEncoder().encodeToString(rsaPrivateKey.getEncoded()));

			// 私钥加密，公钥解密--加密
			PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
			KeyFactory keyFactory = KeyFactory.getInstance("RSA");
			PrivateKey privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
			Cipher cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, privateKey);
			byte[] result = cipher.doFinal(data.getBytes());
			System.out.println("RSA private encode: " + java.util.Base64.getEncoder().encodeToString(result));

			// 私钥加密，公钥解密--解密
			X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
			keyFactory = KeyFactory.getInstance("RSA");
			PublicKey publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			result = cipher.doFinal(result);
			System.out.println("RSA pub decode: " + new String(result));

			// 公钥加密，私钥解密--加密
			x509EncodedKeySpec = new X509EncodedKeySpec(rsaPublicKey.getEncoded());
			keyFactory = KeyFactory.getInstance("RSA");
			publicKey = keyFactory.generatePublic(x509EncodedKeySpec);
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			result = cipher.doFinal(data.getBytes());
			System.out.println("RSA pub encode: " + java.util.Base64.getEncoder().encodeToString(result));

			// 公钥加密，私钥解密--解密
			pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(rsaPrivateKey.getEncoded());
			keyFactory = KeyFactory.getInstance("RSA");
			privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
			cipher = Cipher.getInstance("RSA");
			cipher.init(Cipher.DECRYPT_MODE, privateKey);
			result = cipher.doFinal(result);
			System.out.println("RSA private decode: " + new String(result));
			
			// RSA 私钥签名
			byte[] keyBytes = privateKey.getEncoded();
	        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
	        keyFactory = KeyFactory.getInstance("RSA");
	        privateKey = keyFactory.generatePrivate(keySpec);
	        Signature signature = Signature.getInstance("MD5withRSA");
	        signature.initSign(privateKey);
	        signature.update(data.getBytes());
	        String sign = new String(Base64.encodeBase64(signature.sign()));
	        System.out.println("RSA private sign: " + sign);
	        
	        // RSA 公钥验签
	        byte[] pubKeyBytes = publicKey.getEncoded();
	        X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pubKeyBytes);
	        keyFactory = KeyFactory.getInstance("RSA");
	        publicKey = keyFactory.generatePublic(pubKeySpec);
	        signature = Signature.getInstance("MD5withRSA");
	        signature.initVerify(publicKey);
	        signature.update(data.getBytes());
	        boolean verify = signature.verify(Base64.decodeBase64(sign.getBytes()));
	        System.out.println("RSA pub verify: " + verify);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (SignatureException e) {
			e.printStackTrace();
		}
	}

	private static void AESOperate(String data) {
		try {
			// 生成 key
			KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
			keyGenerator.init(new SecureRandom()); // 生成 5 位 0~9 的随机数
			SecretKey secretKey = keyGenerator.generateKey();
			byte[] key1 = secretKey.getEncoded();
			// key 转换为密钥
			Key key2 = new SecretKeySpec(key1, "AES");

			// 加密
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5padding");
			cipher.init(Cipher.ENCRYPT_MODE, key2);
			byte[] result = cipher.doFinal(data.getBytes());
			BigInteger bigInteger = new BigInteger(1, result);
			System.out.println("JDK AES encode: " + bigInteger.toString(16));

			// 解密
			cipher.init(Cipher.DECRYPT_MODE, key2);
			result = cipher.doFinal(result);
			System.out.println("JDK AES decode: " + new String(result));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
	}

	private static void DESOperate(String data) {
		try {
			// 生成 key
			KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
			keyGenerator.init(56); // 指定 key 长度，同时也是密钥长度(56位)
			SecretKey secretKey = keyGenerator.generateKey(); // 生成 key 的材料
			byte[] key = secretKey.getEncoded(); // 生成 key

			// key 转换成密钥（自己指定字节数组时需要转换）
			DESKeySpec desKeySpec = new DESKeySpec(key);
			SecretKeyFactory factory = SecretKeyFactory.getInstance("DES");
			SecretKey key2 = factory.generateSecret(desKeySpec); // 转换后的密钥

			// 加密
			Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding"); // 算法类型/工作方式/填充方式
			cipher.init(Cipher.ENCRYPT_MODE, key2); // 指定为加密模式
			byte[] result = cipher.doFinal(data.getBytes());
			BigInteger bigInteger = new BigInteger(1, result);
			System.out.println("JDK DES encode: " + bigInteger.toString(16));

			// 解密
			cipher.init(Cipher.DECRYPT_MODE, key2); // 相同密钥，指定为解密模式
			result = cipher.doFinal(result); // 根据加密内容解密
			System.out.println("JDK DES decode: " + new String(result));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (InvalidKeyException e) {
			e.printStackTrace();
		} catch (InvalidKeySpecException e) {
			e.printStackTrace();
		} catch (NoSuchPaddingException e) {
			e.printStackTrace();
		} catch (IllegalBlockSizeException e) {
			e.printStackTrace();
		} catch (BadPaddingException e) {
			e.printStackTrace();
		}
	}

	private static void SHA1Encode(String data) {
		try {
	        // SHA-1 编码
	        MessageDigest md = MessageDigest.getInstance("SHA-1");
	        byte[] digest = md.digest(data.getBytes("UTF-8"));
	        
	        // 二进制转化为十六进制编码
	        String sha1 = String.valueOf(Hex.encodeHex(digest));
	        System.out.println("JDK SHA1: " +  sha1);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	private static void MD5Encode(String data) {
		try {
			// MD5 编码
			MessageDigest md = MessageDigest.getInstance("MD5");
			byte[] digest = md.digest(data.getBytes("UTF-8"));
			// 二进制转化为十六进制编码
	        BigInteger bigInteger = new BigInteger(1, digest);
	        System.out.println("JDK MD5: " +  bigInteger.toString(16));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}

	private static void Base64Operate(String data) {
		try {
			// 加密
			java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();
	        String encoderStr = encoder.encodeToString(data.getBytes("utf-8"));
	        System.out.println("Base64 encode method1: " + encoderStr);
	        byte[] encodeBase64 = Base64.encodeBase64(data.getBytes("UTF-8"));
	        System.out.println("Base64 encode method2: " + new String(encodeBase64));
	        // 解密
	        java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
	        String decoderStr = new String(decoder.decode(encoderStr), "utf-8");
	        System.out.println("Base64 decode method1: " + decoderStr);
	        byte[] decodeBase64 = Base64.decodeBase64(encodeBase64);
	        System.out.println("Base64 decode method2: " + new String(decodeBase64));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
}
